#pragma once
#include <cstdint>
#include <vector>

#include "connect_board.hpp"
#include "rotor_array.hpp"
#include "reflector.hpp"
#include "key.hpp"


namespace clay {
    template<std::uint16_t charset_size, std::uint8_t rotors_number>
    class Enigma {
    private:
        Reflector<charset_size> reflector;
        ConnectBoard<charset_size> connect_board;

        RotorArray<charset_size, rotors_number> rotor_array;
        RotorArray<charset_size, rotors_number> rotor_array_copy;

        void mappingValue(
            std::uint8_t& value,
            RotorArray<charset_size, rotors_number>& using_rotor_array
        )
        {
            using_rotor_array.Roll();

            value = connect_board.Map(value);
            value = using_rotor_array.Map(value);
            value = reflector.Map(value);
            value = using_rotor_array.ReverseMap(value);
            value = connect_board.ReverseMap(value);
        }
    public:
        Enigma(const EnigmaKey<rotors_number>& key)
            : rotor_array(key.rotor_shifts),
              connect_board(key.connect_pair_list)
        {
            rotor_array_copy = rotor_array;
        }


        void operator()(std::vector<std::uint8_t>& values)
        {
            RotorArray<charset_size, rotors_number> using_rotor_array = rotor_array_copy;

            for (std::uint8_t& value : values) {
                mappingValue(value, using_rotor_array);
            }
        }


        void update(std::vector<std::uint8_t>& values)
        {
            for (std::uint8_t& value : values) {
                mappingValue(value, rotor_array);
            }
        }


        std::uint8_t update(std::uint8_t value)
        {
            mappingValue(value, rotor_array);
            return value;
        }


        void update(void *data, const std::size_t data_size)
        {
            assert(data != nullptr);

            std::size_t i;
            std::uint8_t* data_p = static_cast<std::uint8_t*>(data);

            for (i = 0; i < data_size; i++) {
                mappingValue(data_p[i], rotor_array);
            }
        }
    };
}
